﻿using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Design;
using Microsoft.SqlServer.Dts.Runtime;
using System.Collections;
using System.IO;

namespace DatabaseGroup.SSIS.SourceAdapters.Model
{
    /// <summary>
    /// Provides control/access to the component and its configuration
    /// </summary>
    public class ComponentModel : IComponentModel
    {
        private readonly IDTSComponentMetaData100 _componentMetaData;
        private readonly CManagedComponentWrapper _designTimeInstance;
        private readonly IDtsConnectionService _connService;
        private readonly Connections _connections;

        /// <summary>
        /// Initializes a new instance of the ComponentModel class
        /// </summary>
        /// <param name="componentMetaData">The component metadata for accessing component properties and configuration</param>
        /// <param name="connService">Connect service for managing the adding of new connections</param>
        /// <param name="connections">Connections for providing all the currently available connections to the component</param>
        public ComponentModel(IDTSComponentMetaData100 componentMetaData, IDtsConnectionService connService, Connections connections)
        {
            _componentMetaData = componentMetaData;
            _designTimeInstance = _componentMetaData.Instantiate();
            _connService = connService;
            _connections = connections;
        }

        /// <summary>
        /// Gets or sets the name of the component
        /// </summary>
        public string ComponentName
        {
            get { return _componentMetaData.Name; }
            set { _componentMetaData.Name = value; }
        }

        /// <summary>
        /// Gets or sets the description of the component
        /// </summary>
        public string ComponentDescription
        {
            get { return _componentMetaData.Description; }
            set { _componentMetaData.Description = value; }
        }

        /// <summary>
        /// Gets the current connection string the 
        /// component is configured to use
        /// </summary>
        public string ConnectionString
        {
            get
            {
                if (_componentMetaData.RuntimeConnectionCollection.Count > 0 &&
                    !string.IsNullOrEmpty(_componentMetaData.RuntimeConnectionCollection[0].ConnectionManagerID))
                {
                    return _connections[_componentMetaData.RuntimeConnectionCollection[0].ConnectionManagerID].ConnectionString;
                }
                
                return null;
            }
        }

        /// <summary>
        /// Gets the current file connection manager name the
        /// component is configured to use
        /// </summary>
        public string FileConnectionName
        {
            get
            {
                string connectionManagerID = null;

                if (_componentMetaData.RuntimeConnectionCollection.Count > 0)
                    connectionManagerID = _componentMetaData.RuntimeConnectionCollection[0].ConnectionManagerID;

                if (_connections.Contains(connectionManagerID))
                    return _connections[connectionManagerID].Name;
                
                return null;
            }
        }

        /// <summary>
        /// Gets the number of currently configured standard output columns
        /// </summary>
        public int StandardOutputColumns
        {
            get
            {
                IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
                return output.OutputColumnCollection.Count;
            }
        }

        /// <summary>
        /// Gets or sets the components Locale ID
        /// </summary>
        public int Locale
        {
            get
            {
                return _componentMetaData.LocaleID;
            }
            set
            {
                _componentMetaData.LocaleID = value;
            }
        }

        /// <summary>
        /// Gets the value of a component custom property
        /// </summary>
        /// <typeparam name="T">The type of the value stored</typeparam>
        /// <param name="propertyName">The name of the property</param>
        /// <returns>The value of the property</returns>
        public T GetProperty<T>(string propertyName)
        {
            return (T)_componentMetaData.CustomPropertyCollection[propertyName].Value;
        }

        /// <summary>
        /// Sets the value of a component custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="value">The value to set the custom property to</param>
        public void SetProperty(string propertyName, object value)
        {
            _designTimeInstance.SetComponentProperty(propertyName, value);
        }

        /// <summary>
        /// Triggers SSIS to prompt the user to create a new FILE connection manager
        /// </summary>
        /// <returns>The name of the new file connection, or null if none were created</returns>
        public string RequestNewFileConnection()
        {
            ArrayList newFileConnections = _connService.CreateConnection("FILE");

            if (newFileConnections.Count > 0)
                return ((ConnectionManager)newFileConnections[0]).Name;

            return null;
        }

        /// <summary>
        /// Configures the component to use a specific file connection manager
        /// </summary>
        /// <param name="connectionName">The name of the file connection manager to use</param>
        public void SetFileConnectionByName(string connectionName)
        {
            if (string.IsNullOrEmpty(connectionName) && _componentMetaData.RuntimeConnectionCollection[0] != null)
            {
                _componentMetaData.RuntimeConnectionCollection[0].ReleaseConnectionManager();
                return;
            }

            // TODO: MessageStrings
            if (!_connections.Contains(connectionName))
                throw new ArgumentException("Connection manager name was not found under the available list of connections", "connectionName");

            ConnectionManager connectionManager = _connections[connectionName];

            _componentMetaData.RuntimeConnectionCollection[0].ConnectionManagerID = connectionManager.ID;
            _componentMetaData.RuntimeConnectionCollection[0].ConnectionManager = 
                DtsConvert.GetExtendedInterface(connectionManager);
        }

        /// <summary>
        /// Gets the complete file path for a specific connection
        /// </summary>
        /// <param name="connectionName">The name of the file connection manager to use</param>
        /// <returns>The file path for the connection</returns>
        public string GetFileConnectionByName(string connectionName)
        {
            if (!_connections.Contains(connectionName))
                return null;

            return _connections[connectionName].ConnectionString;
        }

        /// <summary>
        /// Validates the current file connection manager
        /// </summary>
        /// <param name="name">The name of the connection manager to validate</param>
        /// <returns>True if the file connection manager points to a valid file</returns>
        public bool IsFileConnectionValid(string name)
        {
            string file = null;
            
            if (_connections.Contains(name))
                file = _connections[name].ConnectionString;

            if (string.IsNullOrEmpty(file))
                return false;

            if (string.IsNullOrEmpty(file) || !File.Exists(file) || File.GetAttributes(file) == FileAttributes.Directory)
                return false;
            
            return true;
        }

        /// <summary>
        /// Deletes all columns in the component assigned to the standard output
        /// </summary>
        public void ClearStandardOutputColumns()
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
            output.OutputColumnCollection.RemoveAll();
        }

        /// <summary>
        /// Inserts a new column to the component standard output based on
        /// the configured file column provided
        /// </summary>
        /// <param name="column">The ConfiguredFileColumn to base the new column from</param>
        public void InsertStandardOutputColumn(ConfiguredFileColumn column)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
            IDTSOutputColumn100 newColumn = output.OutputColumnCollection.New();

            newColumn.Name = column.Name;
            newColumn.SetDataTypeProperties(column.DataType, column.Length, column.Precision, column.Scale, column.CodePage);
            newColumn.ErrorRowDisposition = DTSRowDisposition.RD_RedirectRow;
            newColumn.TruncationRowDisposition = DTSRowDisposition.RD_IgnoreFailure;
        }

        /// <summary>
        /// Returns a list of file connection manager names
        /// that are currently available to the component
        /// </summary>
        /// <returns>The list of file connection manager names</returns>
        public List<string> GetAvailableFileConnectionNames()
        {
            List<string> names = new List<string>();

            foreach (ConnectionManager connectionManager in _connService.GetConnectionsOfType("FILE"))
            {
                names.Add(connectionManager.Name);
            }

            return names;
        }

        /// <summary>
        /// Gets the standard output column name for a column
        /// at a specific column index
        /// </summary>
        /// <param name="columnIndex">The zero-based column index</param>
        /// <returns>The standard output column name</returns>
        public string GetStandardOutputColumnName(int columnIndex)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
            return output.OutputColumnCollection[columnIndex].Name;
        }

        /// <summary>
        /// Gets the disposition settings for a specific standard output column
        /// </summary>
        /// <param name="columnIndex">The zero-based index of the column</param>
        /// <param name="errorDisposition">The column error row disposition</param>
        /// <param name="truncationDisposition">The column truncation row disposition</param>
        public void GetStandardOutputColumnDisposition(int columnIndex,
            out DTSRowDisposition errorDisposition, out DTSRowDisposition truncationDisposition)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
            IDTSOutputColumn100 column = output.OutputColumnCollection[columnIndex];

            errorDisposition = column.ErrorRowDisposition;
            truncationDisposition = column.TruncationRowDisposition;
        }

        /// <summary>
        /// Sets a specific standard output column disposition configuration
        /// </summary>
        /// <param name="columnIndex">The zero-based column index to set the disposition on</param>
        /// <param name="errorDisposition">The column error row disposition to use</param>
        /// <param name="truncationDisposition">The column truncation row disposition to use</param>
        public void SetStandardOutputColumnDisposition(int columnIndex, 
            DTSRowDisposition errorDisposition, DTSRowDisposition truncationDisposition)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];
            IDTSOutputColumn100 column = output.OutputColumnCollection[columnIndex];

            column.ErrorRowDisposition = errorDisposition;
            column.TruncationRowDisposition = truncationDisposition;
        }

        /// <summary>
        /// Sets the disposition configuration for the standard output
        /// </summary>
        /// <param name="errorDisposition">The error row disposition to use</param>
        /// <param name="truncationDisposition">The truncation row disposition to use</param>
        public void SetStandardOutputDisposition(DTSRowDisposition errorDisposition, DTSRowDisposition truncationDisposition)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];

            output.ErrorRowDisposition = errorDisposition;
            output.TruncationRowDisposition = truncationDisposition;
        }

        /// <summary>
        /// Gets the disposition configuration for the standard output
        /// </summary>
        /// <param name="errorDisposition">The error row disposition</param>
        /// <param name="truncationDisposition">The truncation row disposition</param>
        public void GetStandardOutputDisposition(out DTSRowDisposition errorDisposition, out DTSRowDisposition truncationDisposition)
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.OutputName];

            errorDisposition = output.ErrorRowDisposition;
            truncationDisposition = output.TruncationRowDisposition;
        }

        /// <summary>
        /// Queries the component configuration and reconfigures the error output 
        /// </summary>
        public void ReconfigureErrorOutput()
        {
            IDTSOutput100 output = _componentMetaData.OutputCollection[MessageStrings.ErrorOutputName];

            bool includeErrorAuditing = GetProperty<bool>(PropertiesManager.IncludeErrorAuditRowsPropName);
            bool includeErrorValidationMessages = GetProperty<bool>(PropertiesManager.IncludeErrorValidationMessagesPropName);
            bool includeErrorSourceData = GetProperty<bool>(PropertiesManager.IncludeErrorSourceDataPropName);
            DataType errorSourceDataType = GetProperty<DataType>(PropertiesManager.ErrorSourceDataTypePropName);
            int errorSourceDataSize = GetProperty<int>(PropertiesManager.ErrorSourceDataSizePropName);

            if (errorSourceDataType == DataType.DT_TEXT || errorSourceDataType == DataType.DT_NTEXT)
                errorSourceDataSize = 0;

            CommonUtils.ConfigureErrorOutputAuditing(output, includeErrorAuditing, includeErrorValidationMessages, includeErrorSourceData,
                errorSourceDataType, errorSourceDataSize);
        }

        /// <summary>
        /// Queries the component configuration and sets up (or reconfigures)
        /// the log output accordingly
        /// </summary>
        public void SetupLogOutput()
        {
            IDTSOutput100 output;

            try
            {
                output = _componentMetaData.OutputCollection[MessageStrings.LogOutputName];
            }
            catch
            {
                output = null;
            }

            bool includeLogOutput = GetProperty<bool>(PropertiesManager.IncludeLogOutputPropName);
            bool includeLogValidationMessages = GetProperty<bool>(PropertiesManager.IncludeValidationMessagesPropName);
            bool includeLogSourceData = GetProperty<bool>(PropertiesManager.IncludeLogSourceDataPropName);
            DataType logSourceDataType = GetProperty<DataType>(PropertiesManager.LogSourceDataTypePropName);
            int logSourceDataSize = GetProperty<int>(PropertiesManager.LogSourceDataSizePropName);

            if (logSourceDataType == DataType.DT_TEXT || logSourceDataType == DataType.DT_NTEXT)
                logSourceDataSize = 0;

            if (output == null && includeLogOutput)
            {
                output = _componentMetaData.OutputCollection.New();
                output.Name = MessageStrings.LogOutputName;
            }
            else if (output != null && !includeLogOutput)
            {
                _componentMetaData.OutputCollection.RemoveObjectByID(output.ID);
                return;
            }
            else if (output == null)
            {
                return;
            }

            CommonUtils.ConfigureLogOutput(output, includeLogValidationMessages, includeLogSourceData, logSourceDataType, logSourceDataSize);
        }
    }
}
